#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# ----------------------------------------------------------
# -- This's SFTP Utils
# ----------------------------------------------------------
# -- Use this function:
# --
# -- if __name__ == '__main__':
# --    result = sftp_connect(host=sftp_config.host, port=sftp_config.port,
# --        username=sftp_config.username, password=sftp_config.password)
# --    ftp_connect = result[2]
# --    upload = sftp_upload(ftp_connect, sftp_config.localDir, sftp_config.homeDir)
# --    download = sftp_download(ftp_connect, sftp_config.localDir + "/download", sftp_config.homeDir)
# --    ftp_connect.close()
# ****************************
# Author: lmay.Zhou
# Blog: www.lmaye.com
# Email lmay@lmaye.com
# Date: 2018/5/2 20:48 星期三
# ----------------------------------------------------------
import os
import stat
import paramiko
from core.utils import sftp_config
from core import LOG


def sftp_connect(host, port, username, password):
    """
        SFTP 连接

        :param host:        主机
        :param port:        端口
        :param username:    用户名
        :param password:    密码
        :return:            Connect
    """
    LOG.info("SFTP Connection...")
    try:
        ftp_connect = paramiko.Transport((host, port))
        ftp_connect.connect(username=username, password=password)
        result = [1, "connection success", ftp_connect]
    except Exception as e:
        result = [-1, "connection fail, reason:{0}".format(e), None]
    return result


def sftp_upload(ftp_connect, local, remote):
    """
        上传

        :param ftp_connect:
        :param local:   客户端文件或文件夹路径，当路径以localDir开始，文件保存到homeDir的相对路径下
        :param remote:  服务端文件夹相对路径，可以为None、""，此时文件上传到homeDir
        :return:
    """
    LOG.info("start upload file by use SFTP...")
    result = [1, ""]
    sftp = paramiko.SFTPClient.from_transport(ftp_connect)
    try:
        remote_rel_dir = formatPath(remote)
        local_path = formatPath(local)

        local_rel_dir = ""
        if local_path == "":
            local_path = sftp_config.localDir
            local_path = formatPath(local_path)
        else:
            if local_path.startswith(sftp_config.localDir):  # 绝对路径
                local_rel_dir = local_path.replace(sftp_config.localDir, "/")
                local_rel_dir = formatPath(local_rel_dir)
            else:  # 相对(localDir)路径
                local_path = formatPath(sftp_config.localDir, local_path)

        if remote_rel_dir == "":
            remote_rel_dir = formatPath("/uploadFiles/", local_rel_dir)
        else:
            if remote_rel_dir.startswith(sftp_config.homeDir):
                remote_rel_dir = remote_rel_dir.replace(sftp_config.homeDir, "/")
                remote_rel_dir = formatPath(remote_rel_dir)

        if os.path.isdir(local_path):  # isDir
            rs = uploadDir(sftp, remote_rel_dir, local_path)
        else:  # isFile
            rs = uploadFile(sftp, remote_rel_dir, local_path)

        if rs[0] == -1:
            result[0] = -1
        result[1] = rs[1]
    except Exception as e:
        result = [-1, "upload fail, reason:{0}".format(e)]
    sftp.close()
    return result


def sftp_download(ftp_connect, local, remote):
    """
        下载

        :param ftp_connect:
        :param local:   客户端文件夹的绝对路径，如E:/SFTP/downDir/
        :param remote:  服务端文件或文件夹的绝对或相对路径
        :return:
    """
    print("start download file by use SFTP...")
    result = [1, ""]

    sftp = paramiko.SFTPClient.from_transport(ftp_connect)
    try:
        remote_path = formatPath(remote)
        local_abs_dir = formatPath(local)

        if remote_path == "":
            remote_rel = sftp_config.homeDir
        else:
            if remote_path.startswith(sftp_config.homeDir):
                remote_rel = remote_path.replace(sftp_config.homeDir, "/")
                remote_rel = formatPath(remote_rel)
            else:
                remote_rel = remote_path

        if local_abs_dir == "":
            local_abs_dir = sftp_config.localDir
            local_abs_dir = formatPath(local_abs_dir)

        if stat.S_ISREG(sftp.stat(remote_rel).st_mode):  # isFile
            remote_rel_path = remote_rel
            file_name = os.path.basename(remote_rel_path)
            local_abs_path = formatPath(local_abs_dir, file_name)

            lad = os.path.split(local_abs_path)[0]
            lad = formatPath(lad)
            if not os.path.exists(lad):
                os.makedirs(lad)
            sftp.get(remote_rel_path, local_abs_path)
            result = [1, "download " + file_name + " success"]
        else:  # isDir
            remote_rel_dir = remote_rel

            for pd in sftp.listdir(remote_rel_dir):  # pd is dir or file 'name
                rp = formatPath(remote_rel_dir, pd)
                if isDir(sftp, rp):
                    lad = formatPath(local_abs_dir, pd)
                else:
                    lad = local_abs_dir
                rs = sftp_download(ftp_connect, lad, rp)
                result[1] = result[1] + "\n" + rs[1]
                if rs[0] == -1:
                    result[0] = -1
                else:
                    if result[0] != -1:
                        result[0] = 1
    except Exception as e:
        result = [-1, "download fail, reason: {0}".format(e)]
    sftp.close()
    return result


def uploadDir(sftp, remoteRelDir, localAbsDir):
    """
        上传指定文件夹下的所有

        :param sftp:
        :param remoteRelDir:    服务端文件夹相对路径，可以为None、""，此时文件上传到homeDir
        :param localAbsDir:     客户端文件夹路径，当路径以localDir开始，文件保存到homeDir的相对路径下
        :return:
    """
    LOG.info("start upload dir by use SFTP...")
    result = [1, ""]
    try:
        for root, dirs, files in os.walk(localAbsDir):
            if len(files) > 0:
                for fileName in files:
                    local_abs_path = formatPath(localAbsDir, fileName)
                    rs = uploadFile(sftp, remoteRelDir, local_abs_path)
                    if rs[0] == -1:
                        result[0] = -1
                    result[1] = result[1] + "\n" + rs[1]

            if len(dirs) > 0:
                for dirName in dirs:
                    rrd = formatPath(remoteRelDir, dirName)
                    lad = formatPath(localAbsDir, dirName)
                    rs = uploadDir(sftp, rrd, lad)
                    if rs[0] == -1:
                        result[0] = -1
                    result[1] = result[1] + "\n" + rs[1]
            break
    except Exception as e:
        result = [-1, "upload fail, reason:{0}".format(e)]
    return result


def uploadFile(sftp, remoteRelDir, localAbsPath):
    """
        上传指定文件

        :param sftp:
        :param remoteRelDir:    服务端文件夹相对路径，可以为None、""，此时文件上传到homeDir
        :param localAbsPath:    客户端文件路径，当路径以localDir开始，文件保存到homeDir的相对路径下
        :return:
    """
    LOG.info("start upload file by use SFTP...")
    try:
        try:
            sftp.chdir(remoteRelDir)
        except FileNotFoundError:
            try:
                sftp.mkdir(remoteRelDir)
            except FileNotFoundError:
                LOG.error("U have no authority to make dir")
        file_name = os.path.basename(localAbsPath)
        remote_rel_path = formatPath(remoteRelDir, file_name)
        sftp.put(localAbsPath, remote_rel_path)
        result = [1, "upload " + file_name + " success"]
    except Exception as e:
        result = [-1, "upload fail, reason:{0}".format(e)]
    return result


def isDir(sftp, path):
    """
        判断remote path isDir or isFile

        :param sftp:
        :param path:
        :return:
    """
    try:
        sftp.chdir(path)
        return True
    except:
        return False


def lastDir(path):
    """
        return last dir'name in the path, like os.path.basename

        :param path:
        :return:
    """
    path = formatPath(path)
    paths = path.split("/")
    if len(paths) >= 2:
        return paths[-2]
    else:
        return ""


def formatPath(path, *paths):
    """
        格式化路径或拼接路径并格式化

        :param path:    路径1
        :param paths:   路径2-n
        :return:
    """
    if path is None or path == "." or path == "/" or path == "//":
        path = ""

    if len(paths) > 0:
        for pi in paths:
            if pi == "" or pi == ".":
                continue
            path = path + "/" + pi

    if path == "":
        return path

    while path.find("\\") >= 0:
        path = path.replace("\\", "/")
    while path.find("//") >= 0:
        path = path.replace("//", "/")

    if path.find(":/") > 0:  # 含磁盘符 NOT EQ ZERO, os.path.isabs NOT WORK
        if path.startswith("/"):
            path = path[1:]
    else:
        if not path.startswith("/"):
            path = "/" + path

    if os.path.isdir(path):  # remote path is not work
        if not path.endswith("/"):
            path = path + "/"
    elif os.path.isfile(path):  # remote path is not work
        if path.endswith("/"):
            path = path[:-1]
    elif path.find(".") < 0:  # maybe it is a dir
        if not path.endswith("/"):
            path = path + "/"
    else:  # maybe it is a file
        if path.endswith("/"):
            path = path[:-1]
    LOG.info("new path is {}".format(path))
    return path
